#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "general.h"
#include "fifo.h"
#include "stack.h"
#include "stack.h"
#include "scheduler.h"

extern Statistics stat;

/* schedule a task on FIFO f*/
#ifdef FIFO
void schedule (Fifo * f, long nt, void (*routine)(void *), void * arg)
#else
void schedule (Stack * f, long nt, void (*routine)(void *), void * arg)
#endif
{
  Work w;
#ifdef FIFO
  FifoElement_T d;
#else
  StackElement_T d;
#endif
  //unsigned int i; 
  static unsigned int fi = 0;;
  //int fnum = FIFO_LENGTH + 1;
  unsigned long long start, stop;

  start = rdtsc();
  w = malloc(sizeof(Work_T));
  if (w == NULL)
    {
      fprintf(stderr, "schedule() unable to allocate memory!\n");
      exit(-1);
    }
  w->routine = routine;
  w->input = arg;
  d.element = (void *) w;

  /* for (i = 0, fi = 0; i < nt; i++) */
  /*   { */
  /*     if (f[i]->num < fnum) */
  /* 	{ */
  /* 	  fnum = f[i]->num; */
  /* 	  fi = i; */
  /* 	} */
  /*   } */

  //fi++;
  //fi = fi%nt;

#ifdef FIFO
  enqueue(f[fi], &d);  
#else
  push(f[fi], &d);
#endif
  stop = rdtsc();
}

/* schedule a task on FIFO f*/
#ifdef FIFO
void priSchedule (Fifo * f, long nt, void (*routine)(void *), void * arg)
#else
void priSchedule (Stack * f, long nt, void (*routine)(void *), void * arg)
#endif
{
  Work w;
#ifdef FIFO
  FifoElement_T d;
#else
  StackElement_T d;
#endif
  //unsigned int i; 
  static unsigned int fi = 0;;
  //int fnum = FIFO_LENGTH + 1;
  unsigned long long start, stop;

  start = rdtsc();
  w = malloc(sizeof(Work_T));
  if (w == NULL)
    {
      fprintf(stderr, "schedule() unable to allocate memory!\n");
      exit(-1);
    }
  w->routine = routine;
  w->input = arg;
  d.element = (void *) w;

  /* for (i = 0, fi = 0; i < nt; i++) */
  /*   { */
  /*     if (f[i]->num < fnum) */
  /* 	{ */
  /* 	  fnum = f[i]->num; */
  /* 	  fi = i; */
  /* 	} */
  /*   } */

  //fi++;
  //fi = fi%nt;

#ifdef FIFO
  enqueue(f[nt], &d);  
#else
  push(f[nt], &d);
#endif
  stop = rdtsc();
}

#ifdef FIFO
void getWork(Fifo * q, long qid, FifoElement e)
#else
void getWork(Stack * q, long qid, StackElement e)
#endif
{
#ifdef FIFO
  dequeue(q[qid], e);
#else
  pop(q[qid], e);
#endif
}

#ifdef FIFO
void priGetWork(Fifo * q, FifoElement e)
#else
void priGetWork(Stack * q, StackElement e)
#endif
{
#ifdef FIFO
  while (1)
    {
      if(nbdequeue(q[0], e) == 1) return;
      if(nbdequeue(q[1], e) == 1) return;
    }
#else
  
  while (1)
    {
      if (nbpop(q[0], e) == 1) return;
      if (nbpop(q[1], e) == 1) return;
    }
#endif
}



#ifdef FIFO
void terminateProcessors(Fifo * f, int num_thread)
#else
void terminateProcessors(Stack * f, int num_thread)
#endif
{
  int i;
  Work w;
  StackElement_T d;
  //FifoElement_T d;
    
    /* terminate all processors */
  for (i = 0; i < num_thread; i++)
    {
      w = malloc(sizeof(Work_T));
      w->routine = &pthread_exit;
      w->input = NULL;
      d.element = (void *) w;
      //enqueue(f[i], &d);
      push(f[0], &d);
      //push(f[i], &d);
    }
}

/* processor thread */
void * processor (void * input)
{
  ProcArgs pargs;
  long tid;
#ifdef FIFO
  Fifo * f;
  FifoElement_T d;
#else
  Stack * f;
  StackElement_T d;
#endif
  
  Work w;
  void (*routine)(void *);
  void * rargs;
  unsigned long tmask;
  ProcStatus pstat;
  unsigned long long start, stop;

  pargs = (ProcArgs) input;
  pstat = pargs->pstat;
  tid = pargs->tid;
  f = pargs->fifo;
  tmask = 0x1 << tid;  

  pthread_barrier_wait(&pstat->bar);

  while (1)
    {
      /* while (1) */
      /* 	{ */
      /* 	  pthread_mutex_lock(&pstat->lock); */
      /* 	  if (nbdequeue(f, &d) == 1) */
      /* 	    { */
      /* 	      pstat->running |= tmask; */
      /* 	      pthread_mutex_unlock(&pstat->lock); */
      /* 	      break; */
      /* 	    } */
      /* 	  else */
      /* 	    { */
      /* 	      pstat->running &= ~tmask; */
      /* 	      if (pstat->running == 0) */
      /* 		{ */
      /* 		  pthread_mutex_unlock(&pstat->lock); */
      /* 		  terminateProcessors(f, pstat->num_proc); */
      /* 		} */
      /* 	      else */
      /* 		{ */
      /* 		  pthread_mutex_unlock(&pstat->lock); */
      /* 		  /\* might want to sleep *\/ */
      /* 		  continue; */
      /* 		} */
      /* 	    } */
      /* 	} */

      start = rdtsc();      
      priGetWork(f, &d);
      //getWork(f, 0, &d);
      //pop(f[0], &d);
      //pop(f[tid], &d);
      //dequeue(f[tid], &d);
      stop = rdtsc();
      stat.dequeue_time[tid] += (stop - start);
      stat.num_dequeue[tid]++;

      start = rdtsc();
      w = (Work) d.element;
      //if (w == NULL) pthread_exit(NULL);

      routine = w->routine;
      rargs = w->input;
      free(w);
      if ((void*)routine == &pthread_exit)
	{
	  stop = rdtsc();
	  //printf("t%ld %lld\n", tid, stop - start);
	}
      stop = rdtsc();
      stat.schedule_overhead[tid] += stop - start;

      start = rdtsc();
      routine(rargs);
      stop = rdtsc();
      stat.processing_time[tid] += (stop - start);
      //printf("p %lld\n", stop - start);
    }
  pthread_exit(NULL);
}

void launchProcessors(int num_proc, pthread_t threads[], ProcStatus pstat, ProcArgs pargs, Stack * f)
//void launchProcessors(int num_proc, pthread_t threads[], ProcStatus pstat, ProcArgs pargs, Fifo * f)
{
  long t;
  int rc;
  pthread_attr_t attr;

  /* intialize locks and barriers */
  pthread_barrier_init(&pstat->bar, NULL, num_proc);
  pthread_mutex_init(&pstat->lock, NULL);
  /* set number of processors running */
  pstat->num_proc = num_proc;
  /* set all processor status as running */
  pstat->running = 0;
  for (t = 0; t < num_proc; t++)
    {
      pstat->running = pstat->running | (0x1 << t);
    }

  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
  /* create processor threads */
  for (t = 0; t < num_proc; t++)
    {
      printf("Launching processor %ld\n", t);
      pargs[t].tid = t;
      pargs[t].fifo = f;
      pargs[t].pstat = pstat;
      rc = pthread_create(&threads[t], &attr, processor, (void *)&pargs[t]);
      if (rc)
	{
	  printf("ERROR; return code from pthread_create() is %d\n", rc);
	  exit(-1);
	}
    }
}

void collectProcessors(int num_proc, pthread_t threads[], ProcStatus pstat)
{
  long t;
  int rc;
  void *status;
  /* join processor threads */
  for(t = 0; t < num_proc; t++)
    {
      rc = pthread_join(threads[t], &status);
      if (rc)
	{
	  printf("ERROR; return code from pthread_join() is %d\n", rc);
	  exit(-1);
	}
      printf("Collected thread %ld having a status of %ld\n",t,(long)status);
    }

  pthread_mutex_destroy(&pstat->lock);
  pthread_barrier_destroy(&pstat->bar);
  //pthread_exit(NULL);  
}

/* void print_test (void * in) */
/* { */
/*   long a = (long) in; */
/*   printf("%ld\n", a); */
/* } */

/* int main(int argc, char * argv[]) */
/* { */
/*   pthread_t threads[16]; */
/*   ProcArgs_T pargs[16]; */
/*   ProcStatus_T pstat; */
/*   Fifo f; */
/*   long i; */

/*   f = newFifo(); */
/*   /\* enqueue some work *\/ */
/*   for (i = 0; i < 8; i++) */
/*     { */
/*       schedule(f, &print_test, (void *)i); */
/*     } */

/*   launchProcessors(atoi(argv[1]), threads, &pstat, pargs, f); */
/*   collectProcessors(atoi(argv[1]), threads, &pstat); */

/*   pthread_exit(NULL); */
/* } */
